package api

import (
	"errors"
	"github.com/gin-gonic/gin"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	"time"
	"vpom_-system/global"
	"vpom_-system/model/database"
	"vpom_-system/model/request"
	"vpom_-system/model/response"
	"vpom_-system/utils/jwt"
)

type UserApi struct {
}

func (userApi *UserApi) ResetPassword(c *gin.Context) {
	var req request.ResetPassword
	err := c.ShouldBindJSON(&req)
	if err != nil {
		global.Log.Error("Failed to bind JSON:", zap.Error(err))
		response.FailWithMessage(err.Error(), c)
		return
	}
	user, err := userService.UpdateUser(req)
	if err != nil {
		global.Log.Error("Failed to get user message:", zap.Error(err))
		response.FailWithMessage(err.Error(), c)
		return
	}
	response.OkWithData(user, c)
}
func (userApi *UserApi) Login(c *gin.Context) {
	var req request.Login
	if err := c.ShouldBindJSON(&req); err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	err, user := userService.Login(req)
	if err != nil {
		global.Log.Error("Failed to login user:", zap.Error(err))
		response.FailWithMessage("Failed to login user", c)
	} else {
		userApi.TokenNext(c, user)
	}
}
func (userApi *UserApi) Register(c *gin.Context) {
	//接收响应结构体
	var req request.Register
	err := c.ShouldBindJSON(&req)
	if err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}
	user, err := userService.GetUserMsg(req.Username)
	u := request.Register{Username: req.Username, Password: req.Password, Password2: req.Password2}
	err, user = userService.Register(u)
	if err != nil {
		global.Log.Error("Failed to register user:", zap.Error(err))
		response.FailWithMessage("Failed to register user", c)
		return
	}

	userApi.TokenNext(c, user)
}

// TokenNext 生成token
func (userApi *UserApi) TokenNext(c *gin.Context, user database.User) {
	// 生成对应的声明
	accessClaims := request.JwtAccessClaims{
		UserID: user.ID,
	}

	refreshClaims := request.JwtRefreshClaims{
		UserID: user.ID,
	}

	// 获取一个对应的jwt实例
	j := jwt.NewJWT()

	// 生成访问令牌
	accessToken, err := j.CreateAccessToken(accessClaims)
	if err != nil {
		global.Log.Error("Failed to get accessToken:", zap.Error(err))
		response.FailWithMessage("Failed to get accessToken", c)
		return
	}

	// 生成刷新令牌
	refreshToken, err := j.CreateRefreshToken(refreshClaims)
	if err != nil {
		global.Log.Error("Failed to get refreshToken:", zap.Error(err))
		response.FailWithMessage("Failed to get refreshToken", c)
		return
	}

	// 是否开启了多地点登录拦截
	if !global.Config.System.UseMultipoint {
		// 直接设置 RefreshToken 并返回
		jwt.SetRefreshToken(c, refreshToken, int(time.Hour*24*7)) // 7 天过期
		c.Set("user_id", user.ID)
		response.OkWithDetailed(response.Login{
			User:                 user,
			AccessToken:          accessToken,
			AccessTokenExpiresAt: time.Now().Add(time.Hour*24).Unix() * 1000, // 24 小时过期
		}, "Successful login", c)
		return
	}

	// 下面是开启了多点登入拦截
	// 检查 Mongodb 是否已有该用户的 JWT
	// 通过id来判断，在Mongodb中是否存在这个jwt，如果存在，就有两种选择，就是挤掉，或者挤不上去。
	// 挤掉，就是将正在登入的一方的jwt直接删除，换成新的
	// 如果挤不上去，则提示一下，只能等用户登出等，导致这个jwt被加入黑名单或者过期
	if jwtStr, err := jwtService.GetMongodbJWT(user.ID); errors.Is(err, mongo.ErrNoDocuments) {
		// 先检查缓存有吗，如果没有就设置
		if err := jwtService.SetMongodbJWT(refreshToken, user.ID); err != nil {
			//设置失败
			global.Log.Error("Failed to set login status:", zap.Error(err))
			response.FailWithMessage("Failed to set login status", c)
			return
		}
		// 向cookie中设置jwt
		jwt.SetRefreshToken(c, refreshToken, int(time.Hour*24*7))
		c.Set("user_id", user.ID)
		response.OkWithDetailed(response.Login{
			User:                 user,
			AccessToken:          accessToken,
			AccessTokenExpiresAt: time.Now().Add(time.Hour*24).Unix() * 1000, // 24 小时过期
		}, "Successful login", c)
	} else if err != nil {
		// 如果有错误
		global.Log.Error("Failed to get login status:", zap.Error(err))
		response.FailWithMessage("Failed to get login status", c)
	} else {
		// 如果缓存有这个用户的jwt，就将当前这个RefreshToken加入黑名单，避免短暂的泄露
		// 将新的refresh加入缓存，重新将refresh加入cookie当中去
		// 将旧的refresh给取消掉,加入黑名单，目的是防止之前的用户还能登入，其实还有应该把access也加入黑名单，避免短暂的泄露
		// 这样做出来的单用户登入就是会给之前登入的给挤掉
		if err := jwtService.JoinInBlacklist(jwtStr); err != nil {
			global.Log.Error("Failed to invalidate jwt:", zap.Error(err))
			response.FailWithMessage("Failed to invalidate jwt", c)
			return
		}

		// 设置新的 JWT 到 MongoDb
		if err := jwtService.SetMongodbJWT(refreshToken, user.ID); err != nil {
			global.Log.Error("Failed to set login status:", zap.Error(err))
			response.FailWithMessage("Failed to set login status", c)
			return
		}

		// 在设置cookie中的token即可
		jwt.SetRefreshToken(c, refreshToken, int(time.Hour*24*7))
		c.Set("user_id", user.ID)
		response.OkWithDetailed(response.Login{
			User:                 user,
			AccessToken:          accessToken,
			AccessTokenExpiresAt: time.Now().Add(time.Hour*24).Unix() * 1000, // 24 小时过期
		}, "Successful login", c)
	}
}
